package syndicate.gangsters;

import syndicate.gi.*;
import syndicate.util.Util;

import java.util.List;

/**
 * This class is a implementation of the gangster, whose priorities are:
 * 1) earning money(for females); moving to better money place (for males)
 * 2) bearing a baby(for females); earning money(for males)
 * 3) moving to better money place(for females); bearing a baby(for males)
 * 4) moving to the parther(both)
 * 5) moving to better partner place(both)
 * 6) attacking other players' gangsters(males only)
 * 7) giving money our players' gangsters(males only)
 *
 * @author Anna
 */
public class SimpleGangster implements Gangster {
    static final String boss = "Simple";
    static Object bossHandle = null;

    // initial parameters
    public static final float INITIAL_MALE_AUTHORITY = Constants.K_maxauthority / 25;
    public static final float INITIAL_FEMALE_AUTHORITY = Constants.K_maxauthority / 20;

    // and speed
    public static final float INITIAL_MALE_SPEED = Constants.K_maxspeed * 0.9f;
    public static final float INITIAL_FEMALE_SPEED = Constants.K_minspeed * 1.2f;

    // when money level here is less than this - should start looking for better place
    public static final float TOO_LOW_MONEY_LEVEL = Constants.K_bite * INITIAL_FEMALE_AUTHORITY;

    // which money level is acceptable to select this point as new location
    public static final float QUITE_GOOD_MONEY_LEVEL = TOO_LOW_MONEY_LEVEL * 1.5f;

    // only when having this money level(% authority) may give money to other gangsters
    public static final double K_togive = 0.8;

    // local copy of params
    private float authority;
    private float speed;

    // array to store info about other gangsters
    Integer[] otherGangsters;

    // our own id
    Integer id;

    // our partner id
    Integer partherId;

    // population control
    static int maxCount = 400;
    static int currentMaleCount = 0;
    static int currentFemaleCount = 0;

    // this one is the essence of your gangster - it's invoked once a turn
    // and lets your gangster do something
    public synchronized Event makeTurn(GangsterInterface gi) {

        // information about point we're in
        PointInfo currentPointInfo = gi.getPointInfo(this);

        // how much money do we have now
        float gangstersMoney = gi.getMoney(this);

        // how much money available at our current location
        float availMoney = currentPointInfo.getCount(this);

        // making turn according to the sex
        // just to demonstrate that gangsters of different sex can act in different ways
        return (gi.getSex(this) == GangsterSex.Male) ?
                  makeMaleTurn(availMoney, gangstersMoney, currentPointInfo, gi)
                : makeFemaleTurn(availMoney, gangstersMoney, currentPointInfo, gi);
    }

    private Event makeMaleTurn(float availMoney, float gangstersMoney, PointInfo currentPointInfo, GangsterInterface gi) {
        Event ev;

        /* if here's no money to earn,
           going to better place if there's any around
           or to random reachable place otherwise
         */
        if ((ev = maybeMoveForMoney(gi, availMoney, gangstersMoney)) != null) {
            return ev;
        }

        /* getting money if it is needed and possible */
        if ((ev = maybeMoney(availMoney, gangstersMoney)) != null) {
            return ev;
        }

        /* children */
        if ((ev = maybeBearChild(gangstersMoney, gi, currentPointInfo)) != null) {
            return ev;
        }

        /* moving with the partner
           if having one
         */
        if ((ev = maybeMoveToThePartner()) != null) {
            return ev;
        }

        /* if here are no girls,
           moving to one of neighbouring places,
           where are girls
         */
        if ((ev = maybeMoveForPartner(gi, currentPointInfo)) != null) {
            return ev;
        }

        /* attacking other players' gangsters */
        if ((ev = maybeAttack(gi, gangstersMoney, currentPointInfo)) != null) {
            return ev;
        }


        /* giving money to our player's gangsters */
        if ((ev = maybeGiveMoney(gi, gangstersMoney, currentPointInfo)) != null) {
            return ev;
        }

        /* just doing nothing */
        return null;
    }

    private Event makeFemaleTurn(float availMoney, float gangstersMoney, PointInfo currentPointInfo, GangsterInterface gi) {
        Event ev;

        /* getting money if it is needed and possible */
        if ((ev = maybeMoney(availMoney, gangstersMoney)) != null) {
            return ev;
        }

        /* children */
        if ((ev = maybeBearChild(gangstersMoney, gi, currentPointInfo)) != null) {
            return ev;
        }

        /* if here's nothing to earn,
           going to better place if there's any around
           or to random reachable place otherwise
         */
        if ((ev = maybeMoveForMoney(gi, availMoney, gangstersMoney)) != null) {
            return ev;
        }


        /* moving with the partner
           if having one
         */
        if ((ev = maybeMoveToThePartner()) != null) {
            return ev;
        }

        /* if here are no boys,
           moving to one of neighbouring places,
           where are girls
         */
        if ((ev = maybeMoveForPartner(gi, currentPointInfo)) != null) {
            return ev;
        }

        /* just doing nothing */
        return null;
    }

    /* moving with the partner */
    private Event maybeMoveToThePartner() {

        // if having a partner
        if (partherId != null) {
            return new Event(EventKind.ACTION_FOLLOW, partherId);
        }
        return null;
    }


    /* attacking other players' gangsters */
    private Event maybeAttack(GangsterInterface gi, float gangstersMoney, PointInfo pi) {

        // information about other gangsters in this point
        otherGangsters = pi.getEntities(this, otherGangsters);

        for (int i = 0; i < otherGangsters.length; i++) {
            // no more elements
            if (otherGangsters[i] == null) break;
            if (id == null) break;
            int otherGangster = otherGangsters[i];

            // clean otherGangsters array for next use
            otherGangsters[i] = null;

            // if it's other player's gangster
            if (bossHandle != gi.getSyndicate(this, otherGangster)
                    // and we have enough money
                    && gangstersMoney > authority * (Constants.K_fightcost + 2 * Constants.K_min_money)) {

                // - trying to attack
                return new Event(EventKind.ACTION_MAKE_TROUBLE, otherGangster);
            }
        }
        return null;
    }

    /* giving money to other our player's gangsters,
       if having much
    */
    private Event maybeGiveMoney(GangsterInterface gi, float gangstersMoney, PointInfo pi) {

        // information about other gangsters in this point
        otherGangsters = pi.getEntities(this, otherGangsters);

        for (int i = 0; i < otherGangsters.length; i++) {
            // no more elements
            if (otherGangsters[i] == null) break;
            if (id == null) break;
            int otherGangster = otherGangsters[i];

            // clean otherGangsters array for next use
            otherGangsters[i] = null;

            // if it's our player's gangster
            if (bossHandle == gi.getSyndicate(this, otherGangster)) {

                // if it is not me
                if ((otherGangster != this.id) &&
                       // and we have enough money
                       (gangstersMoney > authority * K_togive)) {
                   return new Event(EventKind.ACTION_GIVE_MONEY, otherGangster);
                }
            }
        }
        return null;
    }

    /* if here's no money to earn,
       going to better place if there's any around
       or to random reachable place otherwise
    */
    private Event maybeMoveForMoney(GangsterInterface gi, float availMoney, float gangstersMoney) {
        if (availMoney < TOO_LOW_MONEY_LEVEL
                // and we do need money
            && gangstersMoney < K_togive * authority) {

            // start looking for the better place
            List<PointInfo> neighbourPointsInfo = gi.getNeighbourInfo(this);
            for (PointInfo pointInfo : neighbourPointsInfo) {

                // if one of our neighbour locations have enough evergy
                if (pointInfo.getCount(this) > QUITE_GOOD_MONEY_LEVEL) {
                    return new Event(EventKind.ACTION_MOVE_TO, pointInfo.getLocation());
                }
            }
            // nothing good around, go to random place
            return movetoRandomPlace(gi);
        }
        return null;
    }

    /* if here are no gangsters of different sex,
       moving to one of neighbouring places,
       where they are
    */
    private Event maybeMoveForPartner(GangsterInterface gi, PointInfo currentPointInfo) {

        // if here are no gangsters of other sex
        if (!areHereAnyOtherSexGangsters(gi, currentPointInfo)) {

            // start looking for the better place
            List<PointInfo> neighbourPointsInfo = gi.getNeighbourInfo(this);
            for (PointInfo pointInfo : neighbourPointsInfo) {

                // if there are gangsters of other sex
                if (areHereAnyOtherSexGangsters(gi, pointInfo)) {
                    return new Event(EventKind.ACTION_MOVE_TO, pointInfo.getLocation());
                }
            }
            // nothing good around, moving to random place
            return movetoRandomPlace(gi);
        }
        return null;
    }

    private boolean areHereAnyOtherSexGangsters(GangsterInterface gi, PointInfo pi) {
        // information about other gangsters in this point
        otherGangsters = pi.getEntities(this, otherGangsters);
        for (int i = 0; i < otherGangsters.length; i++) {

            // no more elements
            if (otherGangsters[i] == null) break;
            if (id == null) break;
            int otherGangster = otherGangsters[i];

            // clean otherGangsters array for next use
            otherGangsters[i] = null;

            // if it's our player's gangster
            if ((bossHandle == gi.getSyndicate(this, otherGangster))
                    // of different sex
                    && (gi.getSex(otherGangster) != gi.getSex(this))) {
                return true;
            }
        }
        return false;
    }

    /* moving to random place (of those,
       we could reach)
     */
    private Event movetoRandomPlace(GangsterInterface gi) {
        List<Location> reachLoc = gi.getReachableLocations(this);
        int randomIdx = Util.rnd(reachLoc.size());
        return new Event(EventKind.ACTION_MOVE_TO, reachLoc.get(randomIdx));
    }

    /* earning money if it is needed and possible */
    private Event maybeMoney(float availMoney, float gangstersMoney) {

        // if we do need money
        if (gangstersMoney < authority * (1 - Constants.K_bite)
                // and here is enough money
                && availMoney > Constants.K_bite) {
            return new Event(EventKind.ACTION_EARN_MONEY, authority);
        }
        return null;
    }

    /* bearing a child */
    private Event maybeBearChild(float money, GangsterInterface gi, PointInfo pi) {

        // if our population is not too big yet
        if ((currentMaleCount + currentFemaleCount) < maxCount
                // and we have enough money
                && money > Constants.K_toborn * authority) {
            GangsterSex childSex = getChildSex();
            GangsterParams params = getParams(childSex);

            // searching for a parther
            params.id = getBornPartnerId(gi, pi);

            // what's a pity! There's no parther
            if (params.id == null) {
                return null;
            }

            params.sex = childSex;

            // future child authority
            params.authority = this.authority;

            // future child speed
            params.speed = this.speed;

            return new Event(EventKind.ACTION_BORN, params);
        }
        return null;
    }

    private Integer getBornPartnerId(GangsterInterface gi, PointInfo pi) {
        // information about other gangsters in this point
        otherGangsters = pi.getEntities(this, otherGangsters);

        for (int i = 0; i < otherGangsters.length; i++) {

            // no more elements
            if (otherGangsters[i] == null) break;
            if (id == null) break;
            int otherGangster = otherGangsters[i];

            // clean otherGangsters array for next use
            otherGangsters[i] = null;

            // if it's our player's gangster
            if ((bossHandle == gi.getSyndicate(this, otherGangster))
                    // of different sex
                    && (gi.getSex(otherGangster) != gi.getSex(this))) {

                // let this gangster be our partner
                partherId = otherGangster;
                return otherGangster;
            }
        }

        // no parthers here :(
        return null;
    }

    private GangsterSex getChildSex() {
        // if there's male shortage -- trying to bear a male child
        // else -- a female child
        return (currentFemaleCount > currentMaleCount) ?
                GangsterSex.Male : GangsterSex.Female;
    }

    public synchronized void processEvent(GangsterInterface gi, Event e) {
        switch (e.kind()) {
            case EVENT_BORN:
                processGangsterBorn(gi, e);
                break;
            case EVENT_DEAD:
                processGangsterDead(gi, e);
                break;
            case EVENT_HAVE_TROUBLE:
                processGangsterAttacked(gi, e);
                break;
            case EVENT_MONEY_GIVEN:
                processGangsterMoneyGiven(gi, e);
                break;
            case EVENT_BECAME_PARENT:
                processGangsterBornSuccessful(gi, e);
                break;
            case EVENT_PROPOSED:
                processActionBorn(gi, e);
                break;
            case EVENT_ACTION_FAILED:
                processGangsterActionFailed(gi, e);
                break;
        }
    }

    private void processActionBorn(GangsterInterface gi, Event e) {
        // now just print if someone wanted to bear a child with us
        gi.log(this, e.sender() + " wanted to bear a child with me");
    }

    private void processGangsterActionFailed(GangsterInterface gi, Event e) {
        // now just print if the action is failed
        gi.log(this, "what's a pity!");
    }

    private void processGangsterBornSuccessful(GangsterInterface gi, Event e) {
        // now just print if becoming a parent
        gi.log(this, "having a baby!!!");
    }

    private void processGangsterMoneyGiven(GangsterInterface gi, Event e) {
        // now just print if one gives us a gift
        gi.log(this, "got " + e.param() + " money from " + e.sender());
    }

    private void processGangsterAttacked(GangsterInterface gi, Event e) {
        // now just print if one attacks us
        gi.log(this, "attacked by " + e.sender()
                  + " of " + gi.getSyndicate(this, e.sender())
                  + " damage: " + e.param());
    }

    private void processGangsterDead(GangsterInterface gi, Event e) {
        // gonna get dead, decrease population counter
        if (gi.getSex(this) == GangsterSex.Male) {
            currentMaleCount--;
        } else {
            currentFemaleCount--;
        }
    }

    private void processGangsterBorn(GangsterInterface gi, Event e) {
        // increase population counter
        if (gi.getSex(this) == GangsterSex.Male) {
            currentMaleCount++;
        } else {
            currentFemaleCount++;
        }
        init(gi, gi.getId(this), (GangsterParams) e.param());
    }

    // returns the name of our gangster
    public String getName() {
        return "Bim";
    }

    /*
       this method is invoked once per game,
       to make sure all static are properly inited
     */
    public void reinit(UserGameInfo info) {
        currentMaleCount = (currentFemaleCount = 0);
        bossHandle = null;
	    Util.log("game " + info.kind + " maxTurns=" + info.maxTurns);
    }

    // return our parameters
    public GangsterParams getParams(GangsterSex sex) {
        if (sex == GangsterSex.Male) {
        }
        return new GangsterParams(INITIAL_FEMALE_AUTHORITY, INITIAL_FEMALE_SPEED, id, GangsterSex.Female);
    }

    // who this entity belongs to
    public String getSyndicateName() {
        return boss;
    }

    private void init(GangsterInterface gi, Integer id, GangsterParams params) {
         this.id = id;
         this.authority = params.authority;
         this.speed = params.speed;
         this.otherGangsters = new Integer[1];
         if (bossHandle == null) {
             bossHandle = gi.getSyndicate(this, id);
         }
    }

}

